/**
 * 
 */
package wayic.http;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

/**
 * Abstract implementation of UrlLoaderI. A concrete subclass must implement
 * load(ContentHeaderI header). A subclass which does not implement WithHttpClient
 * should call initConnection(URLConnection) before retrieving InputStream from 
 * a connection.
 * 
 * Note: This class is NOT thread-safe. Provide synchronous access to getAsString()
 * for thread-safety.  
 * 
 * @author Ashesh Nishant
 *
 */
public abstract class AbstractUrlLoader implements UrlLoader {
	
	private final static Logger LOGGER = LogManager.getLogger(AbstractUrlLoader.class);
	protected final static int BUFFER_SIZE = 1024;
	public final static String DEFAULT_ENCODING = "UTF-8";
	protected String encoding = DEFAULT_ENCODING;
	protected byte[] data;
	protected String userAgent;
	protected boolean redirectEnabled;
	protected InputStream in;
	protected int timeout;
	
	
	/**
	 * Default constructor. 
	 * Creates with: default user-agent, true redirectEnabled, infinite timeout. 
	 * for redirectEnabled.
	 */
	public AbstractUrlLoader(){
		this(HttpFactory.WAYIC_USER_AGENT, true, 0);
	}
	
	/**
	 * Full Constructor.
	 * 
	 * @param userAgent
	 * @param redirectEnabled
	 * @param timeout
	 */
	public AbstractUrlLoader(String userAgent, boolean redirectEnabled, int timeout){
		this.userAgent = userAgent;
		this.redirectEnabled = redirectEnabled;
		this.timeout = timeout;
	}
	
	/* (non-Javadoc)
	 * @see wayic.feed.serv.UrlLoaderI#load(wayic.feed.elem.ContentHeaderI)
	 */
	public abstract ContentHeader load(ContentHeader header) throws IOException;
	
	/* (non-Javadoc)
	 * @see wayic.feed.serv.UrlLoaderI#getUrl()
	 */
	public abstract URL getUrl();
	
	/* (non-Javadoc)
	 * @see wayic.feed.serv.UrlLoaderI#getUserAgent()
	 */
	public String getUserAgent(){
		return userAgent;
	}
	
	/* (non-Javadoc)
	 * @see wayic.feed.serv.UrlLoaderI#getConnectionTimeout()
	 */
	public int getConnectionTimeout(){
		return timeout;
	}
	
	/* (non-Javadoc)
	 * @see wayic.feed.serv.UrlLoaderI#isRedirectEnabled()
	 */
	public boolean isRedirectEnabled(){
		return redirectEnabled;
	}
	
	/* (non-Javadoc)
	 * @see wayic.feed.serv.UrlLoaderI#getAsInputStream()
	 */
	public InputStream getAsInputStream() throws IOException{
		if(in == null){
			throw new IOException("InputStream null: called prior to load() or after close()");
		}
		return in;
	}

	/* 
	 * Delegates to getAsReader(encoding).
	 * @see wayic.feed.serv.UrlLoaderI#getAsReader()
	 */
	public Reader getAsReader() throws UnsupportedEncodingException, IOException{
		return getAsReader(getEncoding());
	}

	/* (non-Javadoc)
	 * @see wayic.feed.serv.UrlLoaderI#getAsReader(java.lang.String)
	 */
	public Reader getAsReader(String encoding) throws UnsupportedEncodingException, IOException{
		if(getAsInputStream() == null){
			return null; //this indicates a conditional get failure.
		}
		return new InputStreamReader(getAsInputStream(), encoding);
	}

	/* Delegates to getAsString(encoding).
	 * @see wayic.feed.serv.UrlLoaderI#getAsString()
	 */
	public String getAsString() throws UnsupportedEncodingException, IOException{
		return getAsString(getEncoding());
	}

	/* (non-Javadoc)
	 * @see wayic.feed.serv.UrlLoaderI#getAsString(java.lang.String)
	 */
	public String getAsString(String encoding) throws UnsupportedEncodingException, IOException{
		LOGGER.info("Large buffer may be used. Preferably call getAsReader().");
		if(data == null){
			ByteArrayOutputStream baos = new ByteArrayOutputStream(BUFFER_SIZE);
			byte[] buf = new byte[BUFFER_SIZE];
			int len = 0;
			do {
				if(getAsInputStream() == null){ //only in case of conditional get failure
					return null;
				}
				len = getAsInputStream().read(buf);
				LOGGER.debug("read " + len + " bytes");
				if (len>0) 
					baos.write(buf,0,len);
			} while(len>0);

			data = baos.toByteArray();
		}
		return new String(data, getEncoding());
	}

	/* (non-Javadoc)
	 * @see wayic.feed.serv.UrlLoaderI#getEncoding()
	 */
	public String getEncoding() {
		return encoding;
	}

	/* Delegates to load(ContentHeaderI).
	 * @see wayic.feed.serv.UrlLoaderI#load()
	 */
	public ContentHeader load() throws IOException{
		return load(null);
	}

	/* (non-Javadoc)
	 * @see wayic.feed.serv.UrlLoaderI#setEncoding(java.lang.String)
	 */
	public void setEncoding(String encoding) {
		this.encoding = encoding;
	}
	
	/* (non-Javadoc)
	 * @see wayic.feed.serv.UrlLoaderI#closeConnection()
	 */
	public void close(){
		if(in!=null){
			try {
				in.close();
			} catch (IOException iox) {
				LOGGER.warn("Error closing InputStream",iox);
			} finally{
				in = null;
				data = null;
			}
		}
	}
	
	/**
	 * Initializes the connection with parameters - 
	 * User-Agent, InstanceFollowRedirects, ETag, IfModifiedSince, Timeout. 
	 * Initialization is performed only when a connection is a type of 
	 * HttpUrlConnection.
	 * 
	 * @param con
	 * @param header
	 * @return
	 */
	protected URLConnection initConnection(URLConnection con, ContentHeader header){
		if(con instanceof HttpURLConnection){
			HttpURLConnection httpConn = (HttpURLConnection)con;
			httpConn.setConnectTimeout(timeout);
			httpConn.setInstanceFollowRedirects(redirectEnabled);
			httpConn.setRequestProperty("User-Agent",userAgent);
			if(header != null){
				if(header.getEtag() != null){
					httpConn.setRequestProperty("If-None-Match", header.getEtag());
				}
				if(header.getLastModified() != ContentHeader.UNINITIALIZED){			
					httpConn.setIfModifiedSince(header.getLastModified());				
				}
			}
			return httpConn;
		}
		return con;
	}

}
